package user

import (
	"fmt"
	"net/http"
	"time"

	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-incubator/tidb-dashboard/pkg/model"
	"github.com/pingcap-incubator/tidb-dashboard/pkg/repo"

	"github.com/gin-gonic/gin"
	"go.uber.org/fx"

	"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/utils"
	"github.com/pingcap-incubator/tidb-dashboard/pkg/ldap"
	"github.com/pingcap-incubator/tidb-dashboard/pkg/tidb"
)

var (
	ErrAccountFailed    = ErrNS.NewType("account_err")
	ErrUserInfoFailed   = ErrNS.NewSubNamespace("userinfo_err")
	ErrGetRoleFailed    = ErrUserInfoFailed.NewType("get_role_failed")
	ErrUserInfoBizErr   = ErrUserInfoFailed.NewType("biz_err")
	ErrUserInfoParamErr = ErrUserInfoFailed.NewType("invalid_param")
)

type ServiceParams struct {
	fx.In
	TiDBClient *tidb.Client
	LDAPClient *ldap.Client
}

type Service struct {
	params ServiceParams
}

func NewUserInfoService(p ServiceParams) *Service {
	return &Service{params: p}
}

func RegisterUserInfoRouter(r *gin.RouterGroup, auth *AuthService, s *Service) {
	endpoint := r.Group("/userinfo")

	endpoint.Use(auth.MWAuthRequired())

	endpoint.GET("/ldap", s.queryUserHandler)

	endpoint.Use(utils.MWConnectTiDBDefault(s.params.TiDBClient))

	endpoint.GET("/info/list", s.listUserHandler)
	endpoint.POST("/info", s.createUserHandler)
	endpoint.DELETE("/info", s.deleteUserHandler)
	endpoint.POST("/info/batch", s.batchCreateUserHandler)

	endpoint.GET("/role/list", s.getRoleListHandler)
	endpoint.GET("/role", s.getUserRoleHandler)
	endpoint.PUT("/role", s.saveUserRoleHandler)

	endpoint.GET("/permission", s.getInstancePermissionHandler)
	endpoint.PUT("/permission", s.saveUserInstancePermissionHandler)
}

type ListUserResponse struct {
	Users []UserVo `json:"users"`
}

//nolint
type UserVo struct {
	ID        int        `json:"id"`
	UserID    string     `json:"user_id"`
	Name      string     `json:"name"`
	Roles     []string   `json:"role"`
	LoginTime *time.Time `json:"login_time,omitempty"`
}

// @ID listUser
// @Summary get user list
// @Security JwtAuth
// @Success 200 {object} ListUserResponse
// @Router /userinfo/info/list [get]
func (s *Service) listUserHandler(c *gin.Context) {
	db := utils.GetTiDBConnection(c)
	users, err := repo.GetUsers(db)
	if err != nil {
		_ = c.Error(err)
		return
	}
	userList := make([]UserVo, 0, len(users))
	for _, user := range users {
		result, err := repo.GetUserRole(db, user.UserID)
		if err != nil {
			_ = c.Error(err)
			return
		}
		roles := make([]string, 0, len(result))
		for _, v := range result {
			roles = append(roles, v.Description)
		}
		var loginTime *time.Time
		if user.LoginTime != nil {
			loginTime = user.LoginTime
		}
		userVo := UserVo{
			ID:        int(user.ID),
			UserID:    user.UserID,
			Name:      user.Name,
			Roles:     roles,
			LoginTime: loginTime,
		}
		userList = append(userList, userVo)
	}
	c.JSON(http.StatusOK, &ListUserResponse{Users: userList})
}

type QueryUserRequest struct {
	Query string `form:"query"`
}

type QueryUserResponse struct {
	Users []ldap.User `json:"users"`
}

// @ID queryUser
// @Summary query user
// @Param request query QueryUserRequest true "Request body"
// @Security JwtAuth
// @Success 200 {object} QueryUserResponse
// @Router /userinfo/ldap [get]
func (s *Service) queryUserHandler(c *gin.Context) {
	var req QueryUserRequest
	if err := c.ShouldBindQuery(&req); err != nil {
		utils.MakeInvalidRequestErrorFromError(c, err)
		return
	}
	sessionUser := c.MustGet(utils.SessionUserKey).(*utils.SessionUser)

	if sessionUser.IsSQLUser || sessionUser.IsShared {
		_ = c.Error(ErrAccountFailed.New("请通过丰巢帐号登录操作"))
		c.Status(http.StatusBadRequest)
		return
	}

	userList, err := s.params.LDAPClient.SearchUserInfo(sessionUser.Username, sessionUser.Password, req.Query)

	if err != nil {
		_ = c.Error(err)
		return
	}
	c.JSON(http.StatusOK, &QueryUserResponse{Users: userList})
}

type CreateUserRequest struct {
	UserID string `json:"user_id"`
	Name   string `json:"name"`
}

// @ID createUser
// @Summary create user by userId
// @Param request body CreateUserRequest true "Request body"
// @Security JwtAuth
// @Success 200 "success"
// @Router /userinfo/info [post]
func (s *Service) createUserHandler(c *gin.Context) {
	var req CreateUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.MakeInvalidRequestErrorFromError(c, err)
		return
	}

	t := time.Now()
	user := model.User{
		UserID:     req.UserID,
		Name:       req.Name,
		CreateTime: &t,
	}
	db := utils.GetTiDBConnection(c)

	err := repo.CreateUser(db, &user)
	if err != nil {
		_ = c.Error(err)
		c.Status(http.StatusBadRequest)
		return
	}

	c.String(http.StatusOK, "success")
}

type BatchCreateUserRequest struct {
	UserIDs []string `json:"user_id"`
}

type BatchCreateUserResponse struct {
	UserIDs []string `json:"user_id"`
}

// @ID batchCreateUser
// @Summary create user by userId
// @Param request body BatchCreateUserRequest true "Request body"
// @Security JwtAuth
// @Success 200 {object} BatchCreateUserResponse
// @Router /userinfo/info/batch [post]
func (s *Service) batchCreateUserHandler(c *gin.Context) {
	var req BatchCreateUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.MakeInvalidRequestErrorFromError(c, err)
		return
	}
	sessionUser := c.MustGet(utils.SessionUserKey).(*utils.SessionUser)

	if sessionUser.IsSQLUser || sessionUser.IsShared {
		_ = c.Error(ErrAccountFailed.New("请通过丰巢帐号登录操作"))
		c.Status(http.StatusBadRequest)
		return
	}
	successCreate := make([]string, 0)
	for _, userID := range req.UserIDs {
		err := s.createUserByUserID(c, sessionUser.Username, sessionUser.Password, userID)
		if err != nil {
			log.Error("Create user err", zap.Error(err))
		} else {
			successCreate = append(successCreate, userID)
		}
	}

	c.JSON(http.StatusOK, &BatchCreateUserResponse{UserIDs: successCreate})
}

func (s *Service) createUserByUserID(c *gin.Context, userName string, password, targetUserID string) error {
	ldapUser, err := s.params.LDAPClient.QueryUserInfoByUserID(userName, password, targetUserID)

	if err != nil {
		return err
	}

	if ldapUser == nil {
		return ErrUserInfoBizErr.New("未查询到该用户")
	}

	t := time.Now()
	user := model.User{
		UserID:      ldapUser.ID,
		Name:        ldapUser.Name,
		Phone:       ldapUser.Phone,
		Mail:        ldapUser.Mail,
		Description: ldapUser.Description,
		CreateTime:  &t,
	}
	db := utils.GetTiDBConnection(c)

	err = repo.CreateUser(db, &user)
	if err != nil {
		return err
	}

	return nil
}

type DeleteUserRequest struct {
	UserID string `json:"user_id"`
}

// @ID deleteUser
// @Summary delete user by userId
// @Param request body DeleteUserRequest true "Request body"
// @Security JwtAuth
// @Success 200 "success"
// @Router /userinfo/info [delete]
func (s *Service) deleteUserHandler(c *gin.Context) {
	var req DeleteUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.MakeInvalidRequestErrorFromError(c, err)
		return
	}
	db := utils.GetTiDBConnection(c)
	err := repo.DeleteUserRolesByUserID(db, req.UserID)
	if err != nil {
		_ = c.Error(err)
		return
	}

	err = repo.DeleteInstancePermissionByUserID(db, req.UserID)
	if err != nil {
		_ = c.Error(err)
		return
	}

	err = repo.DeleteUser(db, req.UserID)
	if err != nil {
		_ = c.Error(err)
		return
	}
	c.String(http.StatusOK, "success")
}

type GetRoleListResponse struct {
	Roles []RoleVo `json:"roles"`
}

type RoleVo struct {
	ID          int    `json:"id"`
	Role        string `json:"role"`
	Description string `json:"description"`
}

// @ID getRoleList
// @Summary g
// @Security JwtAuth
// @Success 200 {object} GetRoleListResponse
// @Router /userinfo/role/list [get]
func (s *Service) getRoleListHandler(c *gin.Context) {
	db := utils.GetTiDBConnection(c)
	roles, err := repo.ListRole(db)
	if err != nil {
		_ = c.Error(err)
		return
	}
	roleList := make([]RoleVo, 0, len(roles))
	for _, role := range roles {
		roleVo := RoleVo{
			ID:          int(role.ID),
			Role:        role.Role,
			Description: role.Description,
		}
		roleList = append(roleList, roleVo)
	}
	c.JSON(http.StatusOK, &GetRoleListResponse{Roles: roleList})
}

//nolint
type UserRoleVo struct {
	ID          uint   `json:"id"`
	Role        string `json:"role"`
	RoleID      uint   `json:"role_id"`
	Description string `json:"description"`
}

type GetUserRoleResponse struct {
	UserRoles []UserRoleVo `json:"user_role"`
}

// @ID getUserRole
// @Summary get user role by userId
// @Param user_id query string true "user ID"
// @Security JwtAuth
// @Success 200 {object} GetUserRoleResponse
// @Router /userinfo/role [get]
func (s *Service) getUserRoleHandler(c *gin.Context) {
	userID, ok := c.GetQuery("user_id")
	if !ok {
		_ = c.Error(ErrGetRoleFailed.New("invalid param user_id"))
		return
	}

	db := utils.GetTiDBConnection(c)
	result, err := repo.GetUserRole(db, userID)
	if err != nil {
		_ = c.Error(err)
		return
	}
	roleList := make([]UserRoleVo, 0, len(result))
	for _, role := range result {
		roleVo := UserRoleVo(role)
		roleList = append(roleList, roleVo)
	}
	c.JSON(http.StatusOK, &GetUserRoleResponse{UserRoles: roleList})
}

type SaveUserRoleRequest struct {
	UserID  string `json:"user_id"`
	RoleIDs []uint `json:"role_id"`
}

// @ID saveUserRole
// @Summary get user role by userId
// @Param request body SaveUserRoleRequest true "Request body"
// @Security JwtAuth
// @Success 200 "success"
// @Router /userinfo/role [put]
func (s *Service) saveUserRoleHandler(c *gin.Context) {
	var req SaveUserRoleRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.MakeInvalidRequestErrorFromError(c, err)
		return
	}
	userID := req.UserID
	roleIDs := req.RoleIDs
	db := utils.GetTiDBConnection(c)
	user, err := repo.GetUser(db, userID)
	if err != nil {
		_ = c.Error(err)
		return
	}
	if user == nil {
		_ = c.Error(ErrUserInfoBizErr.New("user not found"))
		return
	}

	//查询该用户当前角色
	result, err := repo.GetUserRole(db, userID)
	if err != nil {
		_ = c.Error(err)
		return
	}
	userRoleIDs := make([]uint, 0, len(result))
	for _, item := range result {
		userRoleIDs = append(userRoleIDs, item.RoleID)
	}

	deleteRoles := utils.NotExists(roleIDs, userRoleIDs)
	log.Info(fmt.Sprint("delete ids", deleteRoles))
	if len(deleteRoles) > 0 {
		err = repo.DeleteUserRoles(db, userID, deleteRoles)
		if err != nil {
			_ = c.Error(err)
			return
		}
	}

	addRoles := utils.NotExists(userRoleIDs, roleIDs)
	log.Info(fmt.Sprint("add ids", addRoles))
	if len(addRoles) > 0 {
		for _, roleID := range addRoles {
			role, err := repo.GetRole(db, roleID)
			if err != nil {
				_ = c.Error(err)
				return
			}
			if role == nil {
				_ = c.Error(ErrGetRoleFailed.New("role not found"))
				return
			}
		}

		for _, roleID := range req.RoleIDs {
			err = repo.InsertUserRole(db, req.UserID, roleID)
			if err != nil {
				_ = c.Error(err)
				return
			}
		}
	}

	c.String(http.StatusOK, "success")
}

type InstancePermissionVo struct {
	ID   uint   `json:"id"`
	Name string `json:"name"`
}

type GetUserInstancePermissionResponse struct {
	Instances []InstancePermissionVo `json:"instances"`
}

// @ID getUserInstancePermission
// @Summary get user instance permission
// @Param user_id query string false "user ID"
// @Security JwtAuth
// @Success 200 {object} GetUserInstancePermissionResponse
// @Router /userinfo/permission [get]
func (s *Service) getInstancePermissionHandler(c *gin.Context) {
	userID, ok := c.GetQuery("user_id")

	db := utils.GetTiDBConnection(c)

	var result []model.TidbInstance
	var err error

	if ok {
		result, err = repo.GetInstancesByUserID(db, userID)
	} else {
		sessionUser := c.MustGet(utils.SessionUserKey).(*utils.SessionUser)

		if sessionUser.IsShared || sessionUser.IsSQLUser {
			result, err = repo.ListAllInstance(db)
		} else {
			result, err = repo.GetInstancesByUserID(db, sessionUser.Username)
		}
	}

	if err != nil {
		_ = c.Error(err)
		return
	}
	instanceList := make([]InstancePermissionVo, 0, len(result))
	for _, instance := range result {
		instanceVo := InstancePermissionVo{
			ID:   instance.ID,
			Name: instance.Name,
		}
		instanceList = append(instanceList, instanceVo)
	}
	c.JSON(http.StatusOK, &GetUserInstancePermissionResponse{Instances: instanceList})
}

type SaveUserInstancePermissionRequest struct {
	UserID     string `json:"user_id"`
	InstanceID []uint `json:"instance_id"`
}

// @ID saveUserInstancePermission
// @Summary save user instance permission
// @Param request body SaveUserInstancePermissionRequest true "Request body"
// @Security JwtAuth
// @Success 200 "success"
// @Router /userinfo/permission [put]
func (s *Service) saveUserInstancePermissionHandler(c *gin.Context) {
	var req SaveUserInstancePermissionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.MakeInvalidRequestErrorFromError(c, err)
		return
	}
	userID := req.UserID
	instanceIDs := req.InstanceID
	db := utils.GetTiDBConnection(c)
	userInfo, err := repo.GetUser(db, userID)
	if err != nil {
		_ = c.Error(err)
		return
	}
	if userInfo == nil {
		_ = c.Error(ErrUserInfoBizErr.New("user not found"))
		return
	}

	//查询该用户当前可以访问的实例
	result, err := repo.GetInstancesByUserID(db, userID)
	if err != nil {
		_ = c.Error(err)
		return
	}
	userInstanceIDs := make([]uint, 0, len(result))
	for _, item := range result {
		userInstanceIDs = append(userInstanceIDs, item.ID)
	}

	deleteInstances := utils.NotExists(instanceIDs, userInstanceIDs)
	if len(deleteInstances) > 0 {
		err = repo.DeleteInstancePermission(db, userID, deleteInstances)
		if err != nil {
			_ = c.Error(err)
			return
		}
	}

	addInstances := utils.NotExists(userInstanceIDs, instanceIDs)
	if len(addInstances) > 0 {
		for _, instanceID := range addInstances {
			instance, err := repo.GetInstance(db, instanceID)
			if err != nil {
				_ = c.Error(err)
				return
			}
			if instance == nil {
				_ = c.Error(ErrUserInfoBizErr.New("instance not found"))
				return
			}
		}

		for _, instanceID := range addInstances {
			err = repo.AddInstancePermission(db, instanceID, req.UserID)
			if err != nil {
				_ = c.Error(err)
				return
			}
		}
	}
	c.String(http.StatusOK, "success")
}
